// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: jhkim

#pragma once

#include <numeric>
#include <cstdlib>
#include <cmath>

#include "coin2/strategy/order_executor/mm_executor.h"

namespace coin2::strategy::order_executor {

using coin2::exchange::base::symbology::IProductInfo;
using coin2::strategy::order_executor::CoolDownLimiter;
using OrderContext = coin2::exchange::base::order::OrderContext;
using OrderList = std::vector<const OrderContext*>;

constexpr double EPSILON = 1e-8;

inline bool IsClose(double a, double b) {
  return std::abs(a - b) < EPSILON * 0.5 * (std::abs(a) + std::abs(b));
}

inline std::vector<double> linespace(const double start, const double ed, const int num) {
  if (num < 2) return std::vector<double>(1, start);
  int partitions = num - 1;
  std::vector<double> pts;
  double length = (ed - start) / partitions;
  pts.push_back(start);
  for (int i = 1; i < num - 1; i++) {
    pts.push_back(start + i * length);
  }
  pts.push_back(ed);
  return pts;
}

inline std::vector<double> LinespacePrice(
    const double start,
    const double end,
    const int num,
    const IProductInfo* pi,
    const int sign) {
  auto round_func = [&](const double& price) {
    return (sign > 0) ? pi->tick_price().RoundDown(price) : pi->tick_price().RoundUp(price);
  };
  if (num < 2) {
    return sign > 0 ? std::vector<double>(1, round_func(end))
                    : std::vector<double>(1, round_func(start));
  }
  int partitions = num;
  std::vector<double> pts;
  double length = (end - start) / partitions;
  if (sign < 0) {
    pts.push_back(round_func(start));
    for (int i = 1; i < num - 1; i++) {
      auto rounded_price = round_func(start + i * length);
      if (rounded_price != pts.back()) pts.push_back(rounded_price);
    }
    if (pts.back() != round_func(end)) pts.push_back(round_func(end));
  } else {
    pts.push_back(round_func(end));
    for (int i = 1; i < num - 1; i++) {
      auto rounded_price = round_func(end - i * length);
      if (rounded_price != pts.back()) pts.push_back(rounded_price);
    }
    if (pts.back() != round_func(start)) pts.push_back(round_func(start));
  }
  return pts;
}
// fit order prices to the distance between orders -> similar to roundDown to tick price
inline std::vector<double> GridLinespacePrice(
    const double start,
    const double end,
    const int num,
    const IProductInfo* pi,
    const int sign,
    const int seed) {
  auto gridCeil = [&](double price, double grid) {
    constexpr double buffer = 1.e-6;
    return std::ceil(price / grid - buffer) * grid;
  };
  auto gridFloor = [&](double price, double grid) {
    constexpr double buffer = 1.e-6;
    return std::floor(price / grid + buffer) * grid;
  };
  auto calGrid = [&](int partitions, double length) {
    constexpr double buffer = 1.e-6;
    double gridLength = length / pi->first_tick_size();
    double gridTick = std::max(std::floor(std::log(gridLength) / std::log(2.0)) - 3, 0.0);
    double minGrid = std::floor(std::pow(2, gridTick) + buffer) * pi->first_tick_size();
    return gridFloor(length, minGrid);
  };
  auto grid_round_func = [&](const double& price, const double& grid) {
    return (sign > 0) ? gridFloor(price, grid) : gridCeil(price, grid);
  };
  if (num < 2) {
    return sign > 0 ? std::vector<double>(1, grid_round_func(end, 5))
                    : std::vector<double>(1, grid_round_func(start, 5));
  }
  int partitions = num;
  std::vector<double> pts;
  double length = (end - start) / partitions;
  double grid = calGrid(partitions, length);
  // if grid is smaller than tick_size, use linespacePrice
  if (grid < pi->first_tick_size()) return LinespacePrice(start, end, num, pi ,sign);
  if (sign < 0) {
    for (int i = 0; i < num; i++) {
      auto rounded_price = grid_round_func(start + i * grid, grid);
      srand(seed + int(rounded_price/grid + 1e-6));
      rounded_price -= sign * (rand()%std::max(int(grid/pi->first_tick_size()/2+1e-6), 1)) * pi->first_tick_size(); 
      if (i==0) pts.push_back(rounded_price);
      if (rounded_price != pts.back()) pts.push_back(rounded_price);
    }
  } else {
    for (int i = 0; i < num; i++) {
      auto rounded_price = grid_round_func(end - i * grid, grid);
      srand(seed + int(rounded_price/grid + 1e-6));
      rounded_price -= sign * (rand()%std::max(int(grid/pi->first_tick_size()/2+1e-6), 1)) * pi->first_tick_size(); 
      if (i==0) pts.push_back(rounded_price);
      if (rounded_price != pts.back()) pts.push_back(rounded_price);
    }
  }
  return pts;
}

inline double getGrid(const double start,
    const double end,
    const int num,
    const IProductInfo* pi) {
      auto gridFloor = [&](double price, double grid) {
        constexpr double buffer = 1.e-6;
        return std::floor(price / grid + buffer) * grid;
      };
      auto calGrid = [&](int partitions, double length) {
        constexpr double buffer = 1.e-6;
        double gridLength = length / pi->first_tick_size();
        double gridTick = std::max(std::floor(std::log(gridLength) / std::log(2.0)) - 3, 0.0);
        double minGrid = std::floor(std::pow(2, gridTick) + buffer) * pi->first_tick_size();
        return gridFloor(length, minGrid);
      };
      int partitions = num;
      double length = (end - start) / partitions;
      double grid = calGrid(partitions, length);
      return std::max(grid, pi->first_tick_size());
    }

static std::random_device rd;
static std::mt19937 gen = std::mt19937(rd());
static std::uniform_real_distribution<double> dis(1.0, 2.0);
static std::uniform_real_distribution<double> dis2(0.5, 1.5);
static std::uniform_real_distribution<double> dis3(0, 1);

struct LayeringLevels {
  enum ComparisonResult {
    TIE = 0,
    INSIDE = 1,
    OUTSIDE = -1,
  };

  enum OperationType {
    NOOP = 0,
    POST = 1,
    CANCEL = -1,
  };

  struct Level {
    static Level ConstructCancelLevel(const OrderContext* wo) {
      if (wo->has_exchange_order_id()) {
        return Level{CANCEL,
                     wo->order_price(),
                     wo->order_qty(),
                     wo->proc_order_id(),
                     wo->exchange_order_id()};
      }
      return Level{CANCEL, wo->order_price(), wo->order_qty(), wo->proc_order_id()};
    }

    bool operator==(const Level& rhs) const {
      return IsClose(price, rhs.price) && IsClose(qty, rhs.qty) && opertype == rhs.opertype;
    }

    OperationType opertype = NOOP;
    double price = 0;
    double qty = 0;
    std::optional<int64_t> proc_order_id;
    std::optional<std::string> exchange_order_id = std::nullopt;
    std::optional<std::string> filter_tag = std::nullopt;
  };
  // Constructor works for qty rounding
  LayeringLevels(
      const IProductInfo* pi,
      int sign,
      const std::vector<double>& prices,
      const std::vector<double>& qtys)
      : pi(pi), sign(sign) {
    for (int i = 0; i < prices.size(); ++i) {
      const auto& price = prices[i];
      double qty_round = pi->tick_qty().Round(qtys[i]);
      if (qty_round < pi->tick_qty().submittable_min_qty(price)) continue;
      if (levels.empty() || price != levels.back().price) {
        levels.push_back({POST, price, qty_round, std::nullopt});
      }
    }
  }
  // Constructor with pair (no filter_tag)
  LayeringLevels(
      const IProductInfo* pi,
      int sign,
      const std::vector<std::pair<double, double>>& pqs)
      : pi(pi), sign(sign) {
    for (const auto& pq : pqs) {
      const auto& price = pq.first;
      double qty_round = pi->tick_qty().Round(pq.second);
      if (qty_round < pi->tick_qty().submittable_min_qty(price)) continue;
      if (levels.empty() || price != levels.back().price) {
        levels.push_back({POST, price, qty_round, std::nullopt});
      }
    }
  }
  // Constructor with struct (with filter_tag)
  LayeringLevels(
      const IProductInfo* pi,
      int sign,
      const std::vector<Level>& pqfiltertag)
      : pi(pi), sign(sign) {
    for (const auto& pqf : pqfiltertag) {
      const auto& price = pqf.price;
      double qty_round = pi->tick_qty().Round(pqf.qty);
      if (qty_round < pi->tick_qty().submittable_min_qty(price)) continue;
      if (levels.empty() || price != levels.back().price) {
        levels.push_back({POST, price, qty_round, std::nullopt, std::nullopt, pqf.filter_tag});
      }
    }
  }

  static LayeringLevels EqualSize(
      const IProductInfo* pi,
      int64_t num_level,
      int sign,
      double price_low,
      double price_high,
      double total_qty,
      bool randomize = true) {
    CHECK_LE(price_low, price_high);
    std::vector<double> prices = LinespacePrice(price_low, price_high, num_level, pi, sign);
    std::vector<double> qtys;
    auto price_num = prices.size();
    if (price_num > 0) {
      std::vector<double> qty_norms;
      double norm_sum = 0;
      for (int i = 0; i < price_num; ++i) {
        if (randomize) {
          qty_norms.push_back(dis(gen));
        } else {
          qty_norms.push_back(1);
        }
        norm_sum += qty_norms.back();
      }
      for (int i = 0; i < price_num; ++i) {
        qtys.push_back(qty_norms[i] / norm_sum * total_qty);
      }
    }

    return LayeringLevels(pi, sign, prices, qtys);
  }

  // EqualSize with bbo quote_qty >= min_quote
  static LayeringLevels EqualSize2(
      const IProductInfo* pi,
      int64_t num_level,
      int sign,
      double price_low,
      double price_high,
      double total_qty,
      double min_quote,
      bool randomize = true) {
    CHECK_LE(price_low, price_high);
    std::vector<double> prices = LinespacePrice(price_low, price_high, num_level, pi, sign);
    std::vector<double> qtys;
    auto price_num = prices.size();
    double best_price = prices[0];
    auto best_qty = min_quote / best_price;
    if (price_num > 0) {
      double left_total_qty = total_qty - best_qty;
      if (left_total_qty <= 0) {
        return LayeringLevels(
            pi,
            sign,
            std::vector<double>{best_price},
            std::vector<double>{best_qty});
      }
      if (best_qty > total_qty / price_num) {
        qtys.push_back(best_qty);
        num_level--;
      } else {
        left_total_qty = total_qty;
      }
      std::vector<double> qty_norms;
      double norm_sum = 0;
      for (int i = 0; i < price_num; ++i) {
        if (randomize) {
          qty_norms.push_back(dis(gen));
        } else {
          qty_norms.push_back(1);
        }
        norm_sum += qty_norms.back();
      }
      for (int i = 0; i < qty_norms.size(); ++i) {
        qtys.push_back(qty_norms[i] / norm_sum * left_total_qty);
      }
    }
    return LayeringLevels(pi, sign, prices, qtys);
  }

  static LayeringLevels LinearAscendingSize(
      const IProductInfo* pi,
      int64_t num_level,
      int sign,
      double price_low,
      double price_high,
      double total_qty,
      bool randomize = true) {
    CHECK_LE(price_low, price_high);
    std::vector<double> prices = LinespacePrice(price_low, price_high, num_level, pi, sign);
    std::vector<double> qtys;
    const auto& price_num = prices.size();
    if (price_num > 0) {
      double qty_sum = 0;
      for (int i = 0; i < price_num; ++i) {
        double qty_norm = 0;
        if (randomize) {
          qty_norm = dis2(gen);
        } else {
          qty_norm = 1.0;
        }
        qty_sum += total_qty * qty_norm * 2 * (i + 1) / price_num / (price_num + 1);
        if (qty_sum > total_qty) {
          qtys.push_back(
              total_qty -
              (qty_sum - total_qty * qty_norm * 2 * (i + 1) / price_num / (price_num + 1)));
          break;
        }
        qtys.push_back(total_qty * qty_norm * 2 * (i + 1) / price_num / (price_num + 1));
      }
    }

    return LayeringLevels(pi, sign, prices, qtys);
  }

  static LayeringLevels ConcaveAscendingSize(
      const IProductInfo* pi,
      int64_t num_level,
      int sign,
      double price_low,
      double price_high,
      double total_qty,
      bool randomize = true) {
    CHECK_LE(price_low, price_high);
    int64_t final_num_level = num_level;
    std::vector<double> qtys;
    std::vector<double> prices = LinespacePrice(price_low, price_high, num_level, pi, sign);
    const auto& price_num = prices.size();
    if (price_num > 0) {
      qtys = linespace(0, price_num - 1, price_num);
      std::for_each(qtys.begin(), qtys.end(), [&](double& n) {
        n = std::pow(std::pow(price_num - 1, 2) - std::pow((n - price_num + 1), 2), 0.5) + 1;
      });
      double vecsum = std::accumulate(qtys.begin(), qtys.end(), decltype(qtys)::value_type(0));
      std::for_each(qtys.begin(), qtys.end(), [&](double& n) { n = n / vecsum * total_qty; });
      if (randomize) {
        std::for_each(qtys.begin(), qtys.end(), [&](double& n) { n *= dis2(gen); });
        double qty_sum = 0;
        final_num_level = 0;
        for (int i = 0; i < price_num; ++i) {
          qty_sum += qtys[i];
          final_num_level++;
          if (qty_sum > total_qty) break;
        }
        qtys.erase(qtys.begin() + final_num_level - 1, qtys.end());
        qtys.push_back(
            total_qty - std::accumulate(qtys.begin(), qtys.end(), decltype(qtys)::value_type(0)));
      }
    }

    return LayeringLevels(pi, sign, prices, qtys);
  }

  static LayeringLevels LogspacePrice(
      const IProductInfo* pi,
      int64_t num_level,
      int sign,
      double price_low,
      double price_high,
      double total_qty,
      bool randomize = true) {
    CHECK_LE(price_low, price_high);
    std::vector<double> prices = LinespacePrice(0, price_high - price_low, num_level, pi, sign);
    if (sign < 0) {
      std::for_each(prices.begin(), prices.end(), [&](double& n) {
        n = pi->tick_price().RoundUp(
            -std::pow((std::pow(price_high - price_low, 2) - std::pow(n, 2)), 0.5) + price_high);
      });
    } else {
      std::for_each(prices.begin(), prices.end(), [&](double& n) {
        n = pi->tick_price().RoundDown(
            std::pow((std::pow(price_high - price_low, 2) - std::pow(n, 2)), 0.5) + price_low);
      });
    }
    const auto& price_num = prices.size();
    std::vector<double> qtys;
    if (price_num > 0) {
      std::vector<double> qty_norms;
      double norm_sum = 0;
      for (int i = 0; i < price_num; ++i) {
        if (randomize) {
          qty_norms.push_back(dis(gen));
        } else {
          qty_norms.push_back(1);
        }
        norm_sum += qty_norms.back();
      }
      for (int i = 0; i < price_num; ++i) {
        qtys.push_back(qty_norms[i] / norm_sum * total_qty);
      }
    }

    return LayeringLevels(pi, sign, prices, qtys);
  }

  // Per every call:
  // it will do one of the most urgent operation:
  // a. cancel 1 order
  // b. post 1 order
  // c. no action
  static std::vector<Level> GetMostDesiredModification(
      LayeringLevels layering_levels,
      int64_t timestamp,
      int sign,
      const OrderList& wo_list) {
    double level_bp = 2;
    const auto& levels = layering_levels.levels;
    if (levels.size() >= 2) {
      level_bp =
          1e4 * (levels[1].price - levels[0].price) / (0.5 * (levels[1].price + levels[0].price));
      level_bp = fabs(level_bp);
    }

    std::ostringstream ss;
    ss << "\n TOBE: ";
    for (const auto& level : levels) {
      ss << level.price << " ";
    }
    ss << "\n ASIS: ";
    for (const auto& wo : wo_list) {
      ss << wo->order_price() << " ";
    }
    SPDLOG_INFO_EVERY_NS(60e9, timestamp, "{}", ss.str());
    // LOG(INFO) << ss.str();
    auto woit = wo_list.cbegin();
    auto lvit = levels.cbegin();
    std::vector<LayeringLevels::Level> cancel_levels{};
    OrderList max_outside_orders;
    for (; woit != wo_list.cend() || lvit != levels.cend();) {
      auto comp = TIE;
      if (woit != wo_list.cend() && lvit != levels.cend()) {
        double tolerance_bp = 0;
        if (woit == wo_list.cbegin() || lvit == levels.cbegin()) {
          tolerance_bp = 2;
        } else {
          tolerance_bp = level_bp;
        }
        comp =
            LayeringLevels::ComparePrice((*woit)->order_price(), lvit->price, tolerance_bp, sign);
        // LOG(INFO) << (*woit)->order_price() << " vs " << lvit->price << " : " << comp;
      }
      if (woit == wo_list.cend() || comp == OUTSIDE) {
        if (cancel_levels.size() != 0) return cancel_levels;
        CHECK_EQ(lvit->opertype, POST);
        if (!max_outside_orders.empty() &&
            lvit->price * sign > max_outside_orders.back()->order_price() * sign) {
          cancel_levels.push_back(
              LayeringLevels::Level::ConstructCancelLevel(max_outside_orders.back()));
          return cancel_levels;
        } else if (wo_list.size() >= levels.size()) {  // Full but it tries to POST -> Cancel Most
                                                       // Outside Order
          cancel_levels.push_back(LayeringLevels::Level::ConstructCancelLevel(wo_list.back()));
          return cancel_levels;
        } else {
          return std::vector<LayeringLevels::Level>{*lvit};
        }
      } else if (lvit == levels.cend() || comp == LayeringLevels::INSIDE) {
        cancel_levels.push_back(LayeringLevels::Level::ConstructCancelLevel(*woit));
        if (levels.empty() ||
            (!levels.empty() &&
             levels.cbegin()->price * sign < (*woit)->order_price() * sign)) {  // emergency cancels
          if (woit != wo_list.cend()) max_outside_orders.push_back(*woit);
          ++woit;
        } else {
          return cancel_levels;
        }
      } else if (comp == LayeringLevels::TIE) {
        if (!cancel_levels.empty()) return cancel_levels;
        if (woit != wo_list.cend()) max_outside_orders.push_back(*woit);
        ++lvit;
        ++woit;
      }
    }
    return cancel_levels;
  }

  static ComparisonResult ComparePrice(double lhs, double rhs, double tolerance_bp, int sign) {
    CHECK_GE(tolerance_bp, 0);
    if (sign > 0) {
      return ComparePrice(rhs, lhs, tolerance_bp, -sign);
    } else if (lhs < rhs * (1 - tolerance_bp * 1e-4)) {
      return INSIDE;
    } else if (lhs > rhs * (1 + tolerance_bp * 1e-4)) {
      return OUTSIDE;
    } else {
      return TIE;
    }
  }

  static ComparisonResult ComparePriceQty(
      std::pair<double, double> lhs,
      std::pair<double, double> rhs,
      std::pair<double, double> tolerances,
      std::optional<double> avoid_qty_cancel_percent,
      int sign) {
    if (sign > 0) {
      return ComparePriceQty(rhs, lhs, tolerances, avoid_qty_cancel_percent, -sign);
    }
    if (lhs.first > rhs.first * (1 + tolerances.first * 1e-4)) {
      return OUTSIDE;
    } else if (lhs.first < rhs.first * (1 - tolerances.first * 1e-4)) {
      return INSIDE;
    } else if (std::abs(lhs.second - rhs.second) / rhs.second > tolerances.second * 1e-4) {
      if (avoid_qty_cancel_percent.has_value() &&
          dis3(gen) < avoid_qty_cancel_percent.value() / 100.0)
        return TIE;
      return INSIDE;
    } else {
      return TIE;
    }
  }

  void ClipQty(double max_qty, std::optional<double> lot_size_quote = std::nullopt, bool verbose = false) {
    double qty_sum = 0;
    double amt_sum = 0;
    for (auto it = levels.begin(); it != levels.end(); ++it) {
      if (qty_sum + it->qty > max_qty) {
        it->qty = pi->tick_qty().Round(max_qty - qty_sum);
        if (it->qty <= pi->tick_qty().submittable_min_qty(it->price)) {
          if (verbose)
            SPDLOG_INFO(
                "Under submittable_min_qty, removed: {} <= {}",
                it->qty,
                pi->tick_qty().submittable_min_qty(it->price));
          levels.erase(it, levels.end());
        } else {
          levels.erase(++it, levels.end());
        }
        break;
      }
      qty_sum += it->qty;
      amt_sum += it->qty * it->price;
    }
    if (lot_size_quote.has_value() && lot_size_quote.value() < amt_sum) {
      double rate = lot_size_quote.value() / amt_sum;
      for (auto it = levels.begin(); it != levels.end(); ++it) {
        it->qty = pi->tick_qty().Round(it->qty * rate);
      }
    }
  }
  const IProductInfo* pi;
  int sign;
  std::vector<Level> levels;
};  // namespace coin2::strategy::order_executor

class LayeringExecutor : public PassiveOrderExecutor {
 public:
  LayeringExecutor(
      const PassiveExecutorConfig& config,
      IOrderSubsystem* os,
      std::optional<int64_t> timestamp,
      const TopBook* book,
      GetProductHolderFunc get_product_holder,
      std::optional<double> lot_size_quote = std::nullopt,
      const bool verbose = false)
      : PassiveOrderExecutor(config, os, timestamp, book, get_product_holder),
        lot_size_quote_(lot_size_quote), verbose_{verbose} {
    cancel_cooldown_limiter_ = std::make_unique<CoolDownLimiter>(1e9);
    order_update_period_ = config.order_update_period();
  }

  void UpdateState(int64_t timestamp) {
    PassiveOrderExecutor::UpdateState(timestamp);
    is_wo_ordered_ = false;
  }

  void UpdateOrders(int64_t timestamp) {
    CHECK_GT(timestamp, last_wo_ordered_ts_) << timestamp << " <=" << last_wo_ordered_ts_;
    ordered_buy_list_ = GetBuyOrders();
    ordered_sell_list_ = GetSellOrders();
    auto sort_func = [](int sign) {
      return [sign](const OrderContext* wo1, const OrderContext* wo2) {
        if (wo1->order_price() == wo2->order_price()) {
          return wo1->order_created_time() < wo2->order_created_time();
        }
        if (sign < 0) {
          return wo1->order_price() < wo2->order_price();
        } else {
          return wo1->order_price() > wo2->order_price();
        }
      };
    };
    std::sort(ordered_buy_list_.begin(), ordered_buy_list_.end(), sort_func(1));
    std::sort(ordered_sell_list_.begin(), ordered_sell_list_.end(), sort_func(-1));
    is_wo_ordered_ = true;
    last_wo_ordered_ts_ = timestamp;
  }

  const OrderList& GetBuyOrders() const { return wo_->GetBuyOrders(); }
  const OrderList& GetSellOrders() const { return wo_->GetSellOrders(); }

  const OrderList& GetOrderedBuyOrders() const { return ordered_buy_list_; }
  const OrderList& GetOrderedSellOrders() const { return ordered_sell_list_; }

  const bool& IsWoOrdered() const { return is_wo_ordered_; }

  void LogAsIsToBe(
      const int64_t& ts,
      const int sign,
      const OrderList& wo_list,
      const std::vector<LayeringLevels::Level>& levels) {
    int64_t cooltime = 60e9;
    if (verbose_) cooltime = 0;
    if (sign > 0) {
      if (ts - last_buy_log_ts_ < cooltime) return;
      last_buy_log_ts_ = ts;
    } else {
      if (ts - last_sell_log_ts_ < cooltime) return;
      last_sell_log_ts_ = ts;
    }
    std::stringstream ss;
    ss << "sgn: " << sign << "\nTOBE: ";
    for (const auto& l : levels) {
      ss << l.qty << "@" << l.price << ", ";
    }
    ss << "\nASIS: ";
    for (const auto& wo : wo_list) {
      ss << wo->working_qty() << "@" << wo->order_price() << ", ";
    }
    SPDLOG_INFO(ss.str());
  }

  void UpdateMidP(const double price) { midp_ = price; }

  void ManageSideOrders(int64_t timestamp, int sign, LayeringLevels levels) {
    if (!wo_) {
      SPDLOG_INFO_EVERY_NS(60e9, timestamp, "wo_ not ready");
      return;
    }
    if (!is_wo_ordered_) UpdateOrders(timestamp);
    levels.ClipQty(GetMaximumWorkingQty(sign), lot_size_quote_, verbose_);

    CHECK_EQ(levels.sign, sign);
    const auto& order_list = sign > 0 ? ordered_buy_list_ : ordered_sell_list_;

    auto opers = LayeringLevels::GetMostDesiredModification(levels, timestamp, sign, order_list);
    for (const auto& oper : opers) {
      auto operkey = std::pair<int, LayeringLevels::OperationType>(sign, oper.opertype);
      if (oper.opertype == LayeringLevels::POST &&
          prev_oper_ts_[operkey] + order_update_period_ < timestamp) {
        SPDLOG_INFO("POST) sgn:{} {} @{}", sign, oper.qty, oper.price);
        ExecuteOperation(timestamp, sign, oper);
        prev_oper_ts_[operkey] = timestamp;
      } else if (oper.opertype == LayeringLevels::CANCEL) {
        ExecuteOperation(timestamp, sign, oper);
      }
    }
  }

  double GetMaximumWorkingQty(int sign) {
    double max_pos;
    if (lot_size_quote_.has_value()) {
      max_pos = std::min(
          config_.max_pos(),
          (config_.max_pos() - config_.reserve()) * lot_size_quote_.value() /
                  (midp_ * config_.lot_size()) +
              config_.reserve());
    } else {
      max_pos = config_.max_pos();
    }

    if (sign > 0) {
      if (midp_ == 0.0) return std::max(0.0, config_.max_pos() - GetPosition());
      return std::max(0.0, max_pos - GetPosition());
    } else {
      if (midp_ == 0.0) return std::max(0.0, GetPosition() - config_.min_pos());
      if (lot_size_quote_.has_value()) {
        double min_pos = config_.reserve() - (config_.reserve() - config_.min_pos()) /
                                                 (config_.max_pos() - config_.reserve()) *
                                                 (max_pos - config_.reserve());
        return std::max(0.0, std::min(GetPosition() - min_pos, GetPosition() - config_.min_pos()));
      }
      return GetPosition() - config_.min_pos();
    }
  }

  void ExecuteOperation(int64_t timestamp, int sign, const LayeringLevels::Level& oper) {
    OrderSide side;
    switch (sign) {
      case 1:
        side = OrderSide::BUY_ORDER;
        break;
      case -1:
        side = OrderSide::SELL_ORDER;
        break;
      default:
        NOTREACHED() << sign;
    }
    switch (oper.opertype) {
      case LayeringLevels::CANCEL: {
        if (!os_->gateway()->need_exchange_id_to_cancel() &&
            !cancel_cooldown_limiter_->InCoolDown(timestamp, *oper.proc_order_id)) {
          os_->gateway()->CancelOrder(*oper.proc_order_id);
          cancel_cooldown_limiter_->Update(timestamp, *oper.proc_order_id);
          SPDLOG_INFO(
              "CANCEL) sgn:{} {} @{} order_id: {} exchange_order_id: {}",
              sign,
              oper.qty,
              oper.price,
              *oper.proc_order_id,
              oper.exchange_order_id.has_value() ? oper.exchange_order_id.value() : "N/A");
        } else if (
            os_->gateway()->need_exchange_id_to_cancel() &&
            !cancel_cooldown_limiter_->InCoolDown(timestamp, *oper.proc_order_id)) {
          const auto* oc = os_->processor().order_manager()->FindByProcOrderId(*oper.proc_order_id);
          if (oc->has_exchange_order_id()) {
            os_->gateway()->CancelOrder(*oper.proc_order_id);
            cancel_cooldown_limiter_->Update(timestamp, *oper.proc_order_id);
            SPDLOG_INFO(
                "CANCEL) sgn:{} {} @{} order_id: {} exchange_order_id: {}",
                sign,
                oper.qty,
                oper.price,
                *oper.proc_order_id,
                oper.exchange_order_id.has_value() ? oper.exchange_order_id.value() : "N/A");
          } else {
            // pass, cannot cancel if exchange order id is not ready.
          }
        }
      } break;
      case LayeringLevels::POST: {
        PassiveOrderExecutor::SubmitOrder(timestamp, side, oper.price, oper.qty);
      } break;
      default:
        NOTREACHED() << oper.opertype;
    }
  }
  std::optional<double> GetLotSizeQuote() const { return lot_size_quote_; }

  void ManageAggOrders(
      int64_t timestamp,
      int sign,
      double order_price,
      double qty,
      std::string tag = "") {
    switch (sign) {
      case 1:
        PassiveOrderExecutor::ForceFireBuyOrder(timestamp, order_price, qty, tag);
        break;
      case -1:
        PassiveOrderExecutor::ForceFireSellOrder(timestamp, order_price, qty, tag);
        break;
      default:
        NOTREACHED() << sign;
    }
  }

 private:
  std::map<std::pair<int, LayeringLevels::OperationType>, int64_t> prev_oper_ts_;
  std::optional<double> lot_size_quote_;
  double midp_{0.0};
  int64_t last_buy_log_ts_{0L};
  int64_t last_sell_log_ts_{0L};
  int64_t last_wo_ordered_ts_{0L};
 protected:
  int64_t order_update_period_{0L};
  bool verbose_;
  std::unique_ptr<CoolDownLimiter> cancel_cooldown_limiter_;
  bool is_wo_ordered_{false};
  OrderList ordered_buy_list_;
  OrderList ordered_sell_list_;
};

}  // namespace coin2::strategy::order_executor
